package com.beerandjesus.sportofkings.handicapper.model;

import com.beerandjesus.sportofkings.handicapper.profile.Profile;

import java.util.*;

public class ResultsTableImpl implements ResultsTable {

    private Map<String, Integer> columnHeaders = new HashMap<String, Integer>();
    private Map<Integer, Integer> columnWidthBeforeTab = new HashMap<Integer, Integer>();
    private Map<Integer, Map<Integer, String>> table = new HashMap<Integer, Map<Integer, String>>();
    private final Map<Integer, ResultType> postPosResultType = new HashMap<Integer, ResultType>();
    private Set<Profile> profiles = new HashSet<Profile>();

    private enum DisplayType {
        SIMPLE, BY_TYPE
    }

    public ResultsTableImpl(String[] columnHeaders) {
        int headerIndex = 1;
        Map<Integer, String> headerLine = new HashMap<Integer, String>();
        headerLine.put(0, "PP");
        columnWidthBeforeTab.put(0, 2);
        for (String header : columnHeaders) {
            this.columnHeaders.put(header, headerIndex);
            headerLine.put(headerIndex, header);
            columnWidthBeforeTab.put(headerIndex, header.length());
            headerIndex++;
        }
        // Add profiles at the end of the table
        this.columnHeaders.put("PROFILES", headerIndex);
        headerLine.put(headerIndex, "PROFILES");
        columnWidthBeforeTab.put(headerIndex, 8);
        // Add trainer stats at the end of the table
        headerIndex++;
        this.columnHeaders.put("TRAINER", headerIndex);
        headerLine.put(headerIndex, "TRAINER");
        columnWidthBeforeTab.put(headerIndex, 7);
        table.put(0, headerLine);
    }

    /* (non-Javadoc)
     * @see com.beerandjesus.sportofkings.handicapper.model.ResultTable#putValue(java.lang.Integer, java.lang.String, java.lang.String)
     */
    public void putValue(Integer postPosition, ResultType resultType, String header, String value) {
        postPosResultType.put(postPosition, resultType);
        Map<Integer, String> data = table.get(postPosition);
        if (data == null) {
            data = new HashMap<Integer, String>();
        }
        Integer index = columnHeaders.get(header);
        if (index == null) {
            throw new RuntimeException("Header '" + header + "' has not been defined");
        }
        data.put(index, value);
        table.put(postPosition, data);
        int valueLength = 0;
        if (value != null) {
            valueLength = value.length();
        }
        if (valueLength > columnWidthBeforeTab.get(index)) {
            columnWidthBeforeTab.put(index, valueLength);
        }
    }

    public void putProfiles(Integer postPosition, ResultType resultType, List<Profile> profiles) {
        StringBuilder sb = new StringBuilder("");
        if (profiles != null) {
            for (Profile p : profiles) {
                sb.append("[").append(p.getName());
                sb.append(" - ").append(p.getScore()).append("]");
            }
        }
        putValue(postPosition, resultType, "PROFILES", sb.toString());

    }

    public void putKeyTrainerStatistics(Integer postPosition, ResultType type, List<KeyTrainerStatistics> keyTrainerStatistics) {
        StringBuilder sb = new StringBuilder("");
        if (keyTrainerStatistics != null) {
            for (KeyTrainerStatistics stat : keyTrainerStatistics) {
                sb.append("[").append(stat.getCategory().getDisplay());
                sb.append(" ").append(stat.getWinPercentage()).append("]");
            }
        }
        putValue(postPosition, type, "TRAINER", sb.toString());
    }

    public Set<Profile> getProfiles() {
        return profiles;
    }

    /* (non-Javadoc)
     * @see com.beerandjesus.sportofkings.handicapper.model.ResultTable#getTable()
	 */
    public Map<Integer, Map<Integer, String>> getTable() {
        return table;
    }

    public String toStringSimple() {
        return doToString(DisplayType.SIMPLE);
    }

    public String toStringByType() {
        return doToString(DisplayType.BY_TYPE);
    }

    public String getValue(Integer postPosition, String header) {
        Map<Integer, String> data = table.get(postPosition);
        if (data == null) {
            throw new IllegalArgumentException("No data defined for post position " + postPosition);
        }
        Integer index = columnHeaders.get(header);
        if (index == null) {
            throw new IllegalArgumentException("Column header " + header + " is not defined");
        }
        return data.get(index);
    }

    private String doToString(DisplayType dType) {
        StringBuilder sb = new StringBuilder();

        Map<Integer, String> headers = table.get(0);
        // headers
        int size = headers.keySet().size();
        for (int i = 0; i < size; i++) {
            String header = headers.get(i);
            sb.append(header);
            if (i < size - 1) {
                int width = columnWidthBeforeTab.get(i);
                for (int j = header.length(); j < (width + 2); j++) {
                    sb.append(" ");
                }
            }
        }
        sb.append("\n");
        // dashes
        for (int i = 0; i < size; i++) {
            int width = columnWidthBeforeTab.get(i);
            for (int j = 0; j < width; j++) {
                sb.append("-");
            }
            if (i < size - 1) {
                sb.append("  ");
            }
        }
        sb.append("\n");
        // rows
        StringBuilder rows = new StringBuilder();
        List<Integer> posts;
        switch (dType) {
            case BY_TYPE:
                posts = sortKeysByType(table.keySet());
                break;
            default:
                posts = sortKeys(table.keySet());
        }
        String lastType = "";
        for (Integer post : posts) {
            if (dType.equals(DisplayType.BY_TYPE) && post != 0) {
                String type = postPosResultType.get(post).toString();
                if (!lastType.equals(type)) {
                    if (!lastType.equals("")) {
                        rows.append("\n");
                    }
                    rows.append(type).append("\n");
                    rows.append(sb.toString());
                    lastType = type;
                }
            } else if (dType.equals(DisplayType.SIMPLE) && post == 0) {
                rows.append(sb.toString());
            }
            if (post == 0) continue;
            rows.append(post).append("  ");
            if (post < 10) {
                rows.append(" ");
            }
            Map<Integer, String> data = table.get(post);
            for (int i = 1; i <= size; i++) {
                String val = data.get(i);
                if (val != null) {
                    rows.append(val);
                } else {
                    val = "";
                }
                if (i < size - 1) {
                    int width = columnWidthBeforeTab.get(i);
                    for (int j = val.length(); j < width; j++) {
                        rows.append(" ");
                    }
                    rows.append("  ");
                }
            }
            rows.append("\n");
        }


        return rows.toString();
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();

        Map<Integer, String> headers = table.get(0);
        int size = headers.keySet().size();
        for (int i = 0; i < size; i++) {
            sb.append(headers.get(i));
            if (i < size - 1) {
                sb.append("\t");
            }
        }
        sb.append("\n");

        List<Integer> posts = sortKeysByType(table.keySet());
        for (Integer post : posts) {
            if (post == 0) continue;
            sb.append(post).append("\t");
            Map<Integer, String> data = table.get(post);
            for (int i = 1; i <= size; i++) {
                String val = data.get(i);
                if (val != null) {
                    sb.append(val);
                }
                if (i < size - 1) {
                    sb.append("\t");
                }
            }
            sb.append("\n");
        }
        return sb.toString();
    }

    public List<Integer> sortKeysByType(Set<Integer> keys) {
        List<Integer> posts = new ArrayList<Integer>(keys);
        Collections.sort(posts, new Comparator<Integer>() {
            public int compare(Integer o1, Integer o2) {
                if (o1 == 0) {
                    return 1;
                } else if (o2 == 0) {
                    return -1;
                }
                ResultType t1 = postPosResultType.get(o1);
                ResultType t2 = postPosResultType.get(o2);
                String s1 = t1.toString() + o1.toString();
                String s2 = t2.toString() + o2.toString();
                return s1.compareTo(s2);
            }

        });
        return posts;
    }

    public List<Integer> sortKeys(Set<Integer> keys) {
        List<Integer> posts = new ArrayList<Integer>(keys);
        Collections.sort(posts, new Comparator<Integer>() {
            public int compare(Integer o1, Integer o2) {
                return o1 - o2;
            }

        });
        return posts;
    }


}
